#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define true  1
#define false 0

typedef void*(*funcT)(void*);

pthread_mutex_t g_mtxA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t g_mtxB = PTHREAD_MUTEX_INITIALIZER;

unsigned char g_bShowDeadlock = 1;

void doWork(const char funcName)
{
    printf ("threadFunc%c long work started...\n", funcName);
    sleep(20);
    printf ("threadFunc%c long work finished\n", funcName);
}

void* threadFunc(void* arg)
{
    char aName = *((char*)arg);
    char bName = (aName == 'A' ? 'B' : 'A');
    printf("threadFunc aName=%c, bName=%c\n", aName, bName);

    pthread_mutex_t *pmtxA = (aName == 'A' ? &g_mtxA: &g_mtxB),
                  *pmtxB = (aName == 'A' ? &g_mtxB: &g_mtxA);

    if (g_bShowDeadlock)
    {
        // deadlock can happen if we miss unlocking after finishing long work
        printf ("threadFunc%c locking g_mtx%c\n", aName, aName);
        pthread_mutex_lock (pmtxA);
        printf ("threadFunc%c locking g_mtx%c\n", aName, bName);
        pthread_mutex_lock (pmtxB);
    }
    else
    {
        // checking for mutexes to be free in infinite loop
        while (1) {
            pthread_mutex_lock (pmtxA);
            if (pthread_mutex_trylock(pmtxB) != EBUSY)
            {
                printf ("threadFunc%c mutexes are free both, switching to work\n", aName, bName);
                break;
            }

            pthread_mutex_unlock (pmtxA);
            printf ("threadFunc%c mutex %c is locked, waiting...\n", aName, bName);
            // making short waiting between each check
            usleep(100);
        }
    }

    // performing long operation and unlocking mutexes
    doWork(aName);

    printf ("threadFunc%c unlocking g_mtx%c\n", aName, bName);
    pthread_mutex_unlock(pmtxB);
    printf ("threadFunc%c unlocking g_mtx%c\n", aName, aName);
    pthread_mutex_unlock(pmtxA);

    printf ("threadFunc%c done\n", aName);
    return 0;
}

unsigned char createThread(pthread_t *pThreadId, funcT threadF, char* threadName)
{
    printf ("creating thread%c\n", *threadName);
    int result = pthread_create (pThreadId, 0, threadF, threadName);
    if (result != 0)
    {
        fprintf (stderr,
                 "could not pthread_create thread %c.  Error: %d/%s\n",
                 *threadName, result, strerror(result));
        return 0;
    }
    return 1;
}

void joinThread(pthread_t *pthreadId, const char threadName)
{
    printf ("waiting to join with thread %c...\n", threadName);
    void *retVal;
    int result = pthread_join (*pthreadId, &retVal);
    if (result != 0)
    {
        fprintf (stderr, "error joining thread %c.  Error: %d/%s\n",
                 threadName, result, strerror(result));
    }
    printf ("joined with thread %c\n", threadName);
}

int main (int argc, char** argv)
{
    if (argc > 1)
    {
        printf("argv[1]=\"%s\"\n", argv[1]);
        g_bShowDeadlock = ((strcmp(argv[1], "0") == 0) ? false : true);
        printf("g_bShowDeadlock=%s\n", (g_bShowDeadlock ? "true" : "false"));
    }

    pthread_t threadIdA, threadIdB;

    printf ("creating threads\n");

    char names[] = {"AB"};
    if (!createThread(&threadIdA, threadFunc, &names[0]))
        return EXIT_FAILURE;
    if (!createThread(&threadIdB, threadFunc, &names[1]))
        return EXIT_FAILURE;

    joinThread(&threadIdA, 'A');
    joinThread(&threadIdB, 'B');

    return EXIT_SUCCESS;
}
